package com.fat.simulator.day.crosstrader;

import java.io.IOException;
import java.util.Calendar;
import java.util.Date;
import java.util.Random;

import com.fat.BankAccount;
import com.fat.Constants;
import com.fat.DataPoint;
import com.fat.DataSet;
import com.fat.LogFile;
import com.fat.Utilities;

public class TradeManager 
{
	private Double stop = 0.0;
	private Double entry = 0.0;
	private Double limit = 0.0;
	private Double pipStopLoss = 0.0;
	private Double pipCost = 0.0;
	private Double entryAmountK = 0.0;
	private Double tradeCost = 0.0;
	private String tradeType = Constants.EMPTY_STR;	// Long or Short
	private int numberOfTrades = 0;
	private int numberOfEntries = 0;
	
	private Boolean entryOrderPlaced = false;
	private Boolean tradeTriggered = false;
	private Boolean managingTrade = false;
	private DataPoint dataPoint = new DataPoint();
	
	private BankAccount bank = null;
	private static DataSet dataSet;
	
	private Double currentDollarDrawDown = 0.0;
	private Double currentDollarTradeLoss = 0.0;
	
	private static final Double pipCostPerK = 0.1;	// 10 cents
	private static final Double pipPaddingEntry = 0.000;
	private static final Double pipPaddingStop = 0.000;
	private static final Double marginAountPerK = 30.0;
	private static final Double spread = 10.0;	// pips
	private static final Double rollB = -0.14;
	private static final Double rollS = 0.03;
	
	private LogFile logFile = null;

	public TradeManager (BankAccount bank, Date startDate, Date endDate, LogFile logFile)
	{
		this.bank = bank;
		dataSet = new DataSet(startDate, endDate, Constants.DATA_TYPE_DAY, "data\\EURUSD\\TradeStationDailyHistData.txt");
		this.logFile = logFile;
	}
	
	public void processDataPoint(Calendar cal) throws IOException
	{
		dataPoint = dataSet.getDataPoint(cal.getTime());
		String emaTrend = dataPoint.getEmaTrend();
		Double maxEMADiff = dataPoint.getMaxEmaDiff();
		
		while (dataPoint == null)
		{
			cal.add(Calendar.DATE, 1);
			dataPoint = dataSet.getDataPoint(cal.getTime());
		}
		
		// If we are currently managing a trade
		if (getManagingTrade())	
		{
			Boolean trendsCrossed = false;
			if ((Constants.TRADE_TYPE_LONG.equals(tradeType) && Constants.TRADE_TYPE_SHORT.equals(emaTrend) && dataPoint.getCloseBid() > dataPoint.getOpenBid()) ||
				(Constants.TRADE_TYPE_SHORT.equals(tradeType) && Constants.TRADE_TYPE_LONG.equals(emaTrend) && dataPoint.getCloseBid() < dataPoint.getOpenBid()))
			{
				trendsCrossed = true;
			}
			
			String crossed = Utilities.emaCrossed(dataSet, dataPoint.getDate());

			if ((Constants.TRADE_TYPE_LONG.equals(tradeType) && Constants.TRADE_TYPE_SHORT.equals(crossed)) ||
				(Constants.TRADE_TYPE_SHORT.equals(tradeType) && Constants.TRADE_TYPE_LONG.equals(crossed)))
			{
				moveStop();
				closeTrade();
			}
		}
		// We have entered a trade, see if the current data point triggered 
		// the trade to execute so that we know if we need to manage it
		else if (getEntryOrderPlaced())
		{
			checkForTradeTrigger();
		}
		// We are looking for a suitable entry point (both Long and Short entries)
		else	
		{
			String crossed = Utilities.emaCrossed(dataSet, dataPoint.getDate());
			
			if (Constants.TRADE_TYPE_LONG.equals(crossed))
			{
				tradeType = Constants.TRADE_TYPE_LONG;
				placeEntryOrder();
			}
			else if (Constants.TRADE_TYPE_SHORT.equals(crossed))
			{
				tradeType = Constants.TRADE_TYPE_SHORT;
				placeEntryOrder();
			}
			else
			{
				logFile.writeLine("No suitable entry found");
			}
		}
	}

	public void placeEntryOrder() throws IOException
	{				
		entryOrderPlaced = true;
		numberOfEntries++;
		
		if (Constants.TRADE_TYPE_LONG.equals(tradeType))
		{
			stop = dataPoint.getLowBid() - pipPaddingStop;
			entry = dataPoint.getHighBid() + pipPaddingEntry;
		}
		else if (Constants.TRADE_TYPE_SHORT.equals(tradeType))
		{
			stop = dataPoint.getHighBid() + pipPaddingStop;
			entry = dataPoint.getLowBid() - pipPaddingEntry;	
		}
		
		pipStopLoss = Math.abs(entry - stop)/0.0001;
		pipCost = bank.getBalance() * bank.getRisk() / pipStopLoss; // bank.getRiskAmount() / pipStopLoss;
		entryAmountK = pipCost / pipCostPerK;
	
		tradeCost = pipCost * spread;
		
		logFile.newLine();		
		logFile.writeLine("Entry Order Placed on " + dataPoint.getDate());
	}
	
	public void checkForTradeTrigger() throws IOException
	{
		// Both entry and stop were hit
		if ((Constants.  TRADE_TYPE_LONG.equals(tradeType) && dataPoint.getHighBid() > entry && dataPoint.getLowBid() < stop) ||
			(Constants.TRADE_TYPE_SHORT.equals(tradeType) && dataPoint.getLowBid() < entry && dataPoint.getHighBid() > stop))
		{
//			// Random generator to choose whether the entry or stop was hit first (we need intra-day data to determine this for sure)
//		    Random generator = new Random();
//		    int rand = generator.nextInt(10) + 1;
//		    if (rand >= 5.5)
//		    {
//		    	enterTrade();
//		    }
//		    else
//		    {
//		    	stopOutTrade();
//		    }
			stopOutTrade();

		}
		// Entry was hit
		else if ((Constants.TRADE_TYPE_LONG.equals(tradeType) && dataPoint.getHighBid() > entry) ||
			(Constants.TRADE_TYPE_SHORT.equals(tradeType) && dataPoint.getLowBid() < entry))
		{
	    	enterTrade();
		}
		// Stop was hit
		else if ((Constants.TRADE_TYPE_LONG.equals(tradeType) && dataPoint.getLowBid() < stop) ||
			(Constants.TRADE_TYPE_SHORT.equals(tradeType) && dataPoint.getHighBid() > stop))
		{
			stopOutTrade();
		}
		// False alarm, so clear the entry order
		else
		{
			removeEntryOrder();
			
			logFile.writeLine("Entry Order Removed on" + dataPoint.getDate());
		}
	}
	
	private void enterTrade() throws IOException
	{
		numberOfTrades++;
		tradeTriggered = true;
		managingTrade = true;
		currentDollarDrawDown += tradeCost;
		currentDollarTradeLoss += tradeCost;
		double usedMargin = entryAmountK * marginAountPerK;
		bank.setUsedMargin(usedMargin);
		bank.setBalance(bank.getBalance() - tradeCost);
//		moveStop(dataPoint);
		
		logFile.writeLine("Trade Entered: " + dataPoint.getDate());
	}
	
	private void stopOutTrade() throws IOException
	{
		bank.setBalance(bank.getBalance() - bank.getBalance() * bank.getRisk());
		currentDollarDrawDown += bank.getBalance() * bank.getRisk();
		
		currentDollarTradeLoss += bank.getBalance() * bank.getRisk();
		bank.setMaxDollarTradeLoss(currentDollarTradeLoss);
		
		removeEntryOrder();
		
		logFile.writeLine("Trade Stopped Out on " + dataPoint.getDate());
	}
	
	private void removeEntryOrder() throws IOException
	{
		stop = null;
		entry = null;
		limit = null;
		pipCost = null;
		tradeCost = null;
		pipStopLoss = null;
		entryAmountK = null;
		tradeType = Constants.EMPTY_STR;
		entryOrderPlaced = false;
		tradeTriggered = false;
		managingTrade = false;
		currentDollarTradeLoss = 0.0;
	}
	
	public void checkForCloseout() throws IOException
	{
		if (Constants.TRADE_TYPE_LONG.equals(tradeType) && dataPoint.getLowBid() < stop ||
			Constants.TRADE_TYPE_SHORT.equals(tradeType) && dataPoint.getHighBid() > stop)
		{
			closeTrade();
		}
		else
		{
			moveStop();
		}
	}

	public void closeTrade() throws IOException
	{
		double pipProfit = 0.0;

		if (Constants.TRADE_TYPE_LONG.equals(tradeType))
		{
			pipProfit = (stop - entry)/0.0001;
		}
		else if (Constants.TRADE_TYPE_SHORT.equals(tradeType))
		{
			pipProfit = (entry - stop)/0.0001;
		}
		
		double dollarProfit = pipProfit * pipCost;
		
		if (dollarProfit < currentDollarDrawDown)
		{
			currentDollarDrawDown -= dollarProfit;
			bank.setMaxDrawDown(Math.max(currentDollarDrawDown, bank.getMaxDrawDown()));
		}
		else
		{
			bank.setMaxDrawDown(Math.max(currentDollarDrawDown, bank.getMaxDrawDown()));
			currentDollarDrawDown = 0.0;
		}
		
		if (dollarProfit < 0)
		{
			currentDollarTradeLoss -= dollarProfit;
		}
		bank.setMaxDollarTradeLoss(Math.max(currentDollarTradeLoss, bank.getMaxDollarTradeLoss()));
		currentDollarTradeLoss = 0.0;
		
		double newBalance = bank.getBalance() + dollarProfit;
		double netProfit = newBalance - bank.getStartingBalance();
		
		bank.setBalance(newBalance);
		bank.setNetProfit(netProfit);
		
		double avgDollarProfitPerDataPoint = netProfit / dataPoint.getDataPointNum();
		double avgDollarProfitPerTrade = netProfit / numberOfTrades;
		double avgPercentProfitPerDataPoint = (avgDollarProfitPerDataPoint / bank.getStartingBalance()) * 100;
		double avgPercentProfitPerTrade = (avgDollarProfitPerTrade / bank.getStartingBalance()) * 100;
		
		bank.setAvgDollarProfitPerDataPoint(avgDollarProfitPerDataPoint);
		bank.setAvgDollarProfitPerTrade(avgDollarProfitPerTrade);
		bank.setAvgPercentProfitPerDataPoint(avgPercentProfitPerDataPoint);
		bank.setAvgPercentProfitPerTrade(avgPercentProfitPerTrade);
		bank.setPipCost(pipCost);
		bank.setUsedMargin(0.0);
		
		stop = null;
		entry = null;
		limit = null;
		pipCost = null;
		tradeCost = null;
		pipStopLoss = null;
		entryAmountK = null;
		tradeType = Constants.EMPTY_STR;
		entryOrderPlaced = false;
		tradeTriggered = false;
		managingTrade = false;
		
		logFile.writeLine("Trade Closed on " + dataPoint.getDate() + " for a profit of: " + dollarProfit);
		logFile.newLine();
	}
	
	public void moveStop() throws IOException
	{
		if (Constants.TRADE_TYPE_LONG.equals(tradeType))
		{
			stop = dataPoint.getLowBid() - pipPaddingStop;
		}
		else if (Constants.TRADE_TYPE_SHORT.equals(tradeType))
		{
			stop = dataPoint.getHighBid() + pipPaddingStop;
		}
		
		logFile.writeLine("Stop Moved");
	}
	
	public Double getStop() {
		return stop;
	}

	public void setStop(Double stop) {
		this.stop = stop;
	}

	public Double getEntry() {
		return entry;
	}

	public void setEntry(Double entry) {
		this.entry = entry;
	}

	public Double getLimit() {
		return limit;
	}

	public void setLimit(Double limit) {
		this.limit = limit;
	}

	public Double getPipStopLoss() {
		return pipStopLoss;
	}

	public void setPipStopLoss(Double pipStopLoss) {
		this.pipStopLoss = pipStopLoss;
	}

	public Double getPipCost() {
		return pipCost;
	}

	public void setPipCost(Double pipCost) {
		this.pipCost = pipCost;
	}

	public Double getRiskAmountPerPip() {
		return pipCost;
	}

	public void setRiskAmountPerPip(Double riskAmountPerPip) {
		this.pipCost = riskAmountPerPip;
	}

	public Double getEntryAmountK() {
		return entryAmountK;
	}

	public void setEntryAmountK(Double entryAmountK) {
		this.entryAmountK = entryAmountK;
	}

	public Double getTradeCost() {
		return tradeCost;
	}

	public void setTradeCost(Double tradeCost) {
		this.tradeCost = tradeCost;
	}

	public String getTradeType() {
		return tradeType;
	}

	public void setTradeType(String entryType) {
		this.tradeType = entryType;
	}

	public Boolean getManagingTrade() {
		return managingTrade;
	}

	public void setManagingTrade(Boolean managingTrade) {
		this.managingTrade = managingTrade;
	}

	public int getNumberOfTrades() {
		return numberOfTrades;
	}

	public void setNumberOfTrades(int numberOfTrades) {
		this.numberOfTrades = numberOfTrades;
	}

	public int getNumberOfEntries() {
		return numberOfEntries;
	}

	public void setNumberOfEntries(int numberOfEntries) {
		this.numberOfEntries = numberOfEntries;
	}

	public Boolean getEntryOrderPlaced() {
		return entryOrderPlaced;
	}

	public void setEntryOrderPlaced(Boolean entryOrderPlaced) {
		this.entryOrderPlaced = entryOrderPlaced;
	}

	public Boolean getTradeTriggered() {
		return tradeTriggered;
	}

	public void setTradeTriggered(Boolean tradeTriggered) {
		this.tradeTriggered = tradeTriggered;
	}

	public DataSet getDataSet() {
		return dataSet;
	}

	public static void setDataSet(DataSet dataSet) {
		TradeManager.dataSet = dataSet;
	}

	public BankAccount getBank() {
		return bank;
	}

	public void setBank(BankAccount bank) {
		this.bank = bank;
	}

	public Double getCurrentDollarDrawDown() {
		return currentDollarDrawDown;
	}

	public void setCurrentDollarDrawDown(Double currentDollarDrawDown) {
		this.currentDollarDrawDown = currentDollarDrawDown;
	}

	public Double getCurrentDollarTradeLoss() {
		return currentDollarTradeLoss;
	}

	public void setCurrentDollarTradeLoss(Double currentDollarTradeLoss) {
		this.currentDollarTradeLoss = currentDollarTradeLoss;
	}
}
